use crate::{asset_info::AssetInfo, oeb_unpack::OebUnpack};
use anyhow::Result;
use quickxml_to_serde::{xml_string_to_json, Config};
use scraper::{Html, Selector};
use std::{
    collections::HashMap,
    fs::{self, File},
    io::Read,
};

pub struct OebReader {
    path: String,
    temp_path: String,
    start_content_id: String,
    assets: HashMap<String, AssetInfo>,
}

impl Drop for OebReader {
    fn drop(&mut self) {
        let _ = self.close();
    }
}

impl OebReader {
    pub fn new(path: &str, temp_path: &str) -> Self {
        Self {
            path: path.to_string(),
            assets: HashMap::new(),
            temp_path: temp_path.to_string(),
            start_content_id: String::default(),
        }
    }

    pub fn init(&mut self) -> Result<()> {
        let mut unpack = OebUnpack::new(&self.path);
        let assets = unpack.analysis()?;
        for mut asset in assets {
            let path = unpack.save_to(&asset.content_id, &self.temp_path)?;
            asset.file_location = path;
            self.assets.insert(asset.content_id.to_string(), asset);
        }
        self.start_content_id = unpack.start_content_id();
        Ok(())
    }

    pub fn chapters(&mut self) -> Result<HashMap<String, String>> {
        let mut chapters = HashMap::new();
        if let Some(asset) = self
            .assets
            .values()
            .find(|f| f.content_id == self.start_content_id)
            .cloned()
        {
            if let Some(file_path) = &asset.file_location {
                let mut xml_file = File::open(file_path)?;
                let mut xml_contents = String::new();
                xml_file.read_to_string(&mut xml_contents)?;
                let json = xml_string_to_json(xml_contents, &Config::new_with_defaults())?;
                let spine = &json["package"]["spine"]["itemref"];
                if let Some(arr) = spine.as_array() {
                    for item in arr {
                        let id = item["@idref"].to_string();
                        let title = self.get_chapter_title(&id);
                        chapters.insert(asset.content_id.to_string(), title);
                    }
                } else {
                    let id = spine["@idref"].to_string();
                    let title = self.get_chapter_title(&id);
                    chapters.insert(asset.content_id, title);
                }
            }
        }
        Ok(chapters)
    }

    pub fn get_chapter_content(&mut self, asset_id: &str) -> Result<String> {
        let mut content = String::default();
        if let Some(assets) = self.assets.get(asset_id) {
            if let Some(file_path) = &assets.file_location {
                let html_content = fs::read_to_string(&file_path)?;
                let document = Html::parse_document(&html_content);
                let selector = Selector::parse("body").unwrap();
                let body = document.select(&selector).next().unwrap();
                let body_text = body
                    .children()
                    .filter_map(|node| {
                        if let Some(element_ref) = scraper::ElementRef::wrap(node) {
                            Some(element_ref.html())
                        } else if node.value().is_text() {
                            Some(node.value().as_text().unwrap().to_string())
                        } else {
                            None
                        }
                    })
                    .collect::<Vec<_>>()
                    .join("");
                content.push_str(&body_text);
            }
        }
        Ok(content)
    }

    pub fn get_asset_content(&mut self, asset_id: &str) -> Result<Option<Vec<u8>>> {
        let mut content = None;
        if let Some(assets) = self.assets.get(asset_id) {
            if let Some(file_path) = &assets.file_location {
                let mut file = File::open(file_path)?;
                let mut buffer = Vec::new();
                file.read_to_end(&mut buffer)?;
                content = Some(buffer);
            }
        }
        Ok(content)
    }

    fn get_chapter_title(&mut self, asset_id: &str) -> String {
        let mut title_text = String::default();
        if let Some(assets) = self.assets.get(asset_id) {
            if let Some(file_path) = &assets.file_location {
                let html_content = fs::read_to_string(file_path).unwrap_or_default();
                let document = Html::parse_document(&html_content);
                let title_selector = Selector::parse("title").unwrap();
                title_text = document
                    .select(&title_selector)
                    .next()
                    .map(|e| e.text().collect::<String>())
                    .unwrap_or_else(|| "无标题".to_string());
            }
        }
        title_text
    }

    pub fn close(&mut self) -> Result<()> {
        for (_, asset) in &self.assets {
            if let Some(file_path) = &asset.file_location {
                fs::remove_file(file_path)?;
            }
        }
        self.assets.clear();
        Ok(())
    }
}

#[test]
fn test_reader() {
    let mut instance = OebReader::new("test.oeb", "");
    let _ = instance.init();
    let chapters = instance.chapters();
    println!("chapters: {:?}", chapters);
    for (id, title) in chapters.unwrap_or_default() {
        let content = instance.get_chapter_content(&id);
        println!("title:{}, content: {}", title, content.unwrap());
    }
    let _ = instance.close();
}
